/*
    This file is part of pcapsipdump

    pcapsipdump is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    pcapsipdump is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    ---

    Project's home: http://pcapsipdump.sf.net/
*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <regex.h>
#include <libgen.h>
#include <pcap.h>

#include "calltable.h"
#include "trigger.h"
#include "pcapsipdump.h"
#include "pcapsipdump_strlib.h"
#include "pcapsipdump_lib.h"

#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif

int get_sip_peername(char *data, int data_len, const char *tag, char *caller, int caller_len);
int get_ip_port_from_sdp(const char *sdp, size_t sdplen, in_addr_t *addr, unsigned short *port);
uint32_t get_ssrc (void *ip_packet_data, bool is_rtcp);
long long parse_size_string(char *s);
#ifndef _GNU_SOURCE
void *memmem(const void* haystack, size_t hl, const void* needle, size_t nl);
#endif

calltable *ct;
int verbosity=0;
int opt_t38only=0;

void sigint_handler(int param)
{
    printf("SIGINT received, terminating\n");
    ct->do_cleanup(0);
    exit(1);
}

void sigterm_handler(int param)
{
    printf("SIGTERM received, terminating\n");
    ct->do_cleanup(0);
    exit(1);
}

#define RTPSAVE_NONE 0
#define RTPSAVE_RTP 1
#define RTPSAVE_RTP_RTCP 2
#define RTPSAVE_RTPEVENT 3
#define MAX_PCAP_FILTER_EXPRESSION 1024

/* return 32-bit hash of source/dest IPv4 or IPv6 address based on IP header */
uint32_t hsaddr(void *p) {
	struct iphdr *header_ip = (iphdr*)p;
	struct ipv6hdr *header_ipv6 = (ipv6hdr*)p;
	if (header_ip->version == 4) {
		return header_ip->saddr;
	}
	else {
		// Ad-hoc 128bit -> 32bit hash function
		return (uint32_t)(
			(uint32_t)(header_ipv6->saddr[0]) +
			(uint32_t)(header_ipv6->saddr[1] * 19) +
			(uint32_t)(header_ipv6->saddr[2] * 37) +
			(uint32_t)(header_ipv6->saddr[3] * 109));
	}
}

uint32_t hdaddr(void *p) {
	struct iphdr *header_ip = (iphdr*)p;
	struct ipv6hdr *header_ipv6 = (ipv6hdr*)p;
	if (header_ip->version == 4) {
		return header_ip->daddr;
	}
	else {
		// Ad-hoc 128bit -> 32bit hash function
		return (uint32_t)(
			(uint32_t)(header_ipv6->daddr[0]) +
			(uint32_t)(header_ipv6->daddr[1] * 19) +
			(uint32_t)(header_ipv6->daddr[2] * 37) +
			(uint32_t)(header_ipv6->daddr[3] * 109));
	}
}

bool get_method(const char *data, char *sip_method, size_t sip_method_size) {
	char *p;
	memcpy(sip_method, data, sip_method_size - 1);
	sip_method[sip_method_size - 1] = ' ';
	p = strchr(sip_method, ' ');
	if (p != NULL) {
		*p = '\0';
		for (char *c = sip_method; c < p; c++) {
			if (!isupper(*c))
				goto fail;
		}
		return true;
	}
fail:
	sip_method[0] = '\0';
	return false;
}

bool  get_status(const char* ptr, unsigned long len, int * sip_status) {
	//"SIP/2.0 487 Request Terminated"
	unsigned long l, tl;
	const char* r, * lp;
	const char* tag = "SIP/2.0 ";
	tl = strlen(tag);
	r = (const char*)memmem(ptr, len, tag, tl);
	if (r == NULL) {
		return false;
	}
	else {
		r += tl;
		while (r[0] == ' ') {
			r++;
		}
		lp = (const char*)memmem(r, len - (r - ptr), " ", 1);
		if (lp == NULL) {
			return false;
		}
		else {
			l = lp - r;
		}
	}
	*sip_status = atoi(r);
	return true;
}

int parse_sdp(in_addr_t saddr, const char *sdp, size_t sdplen, calltable_element_ptr ce)
{
    in_addr_t sdp_addr;
    unsigned short port;
    if (! get_ip_port_from_sdp(sdp, sdplen, &sdp_addr, &port)){
		ct->add_ip_port(ce, sdp_addr, port);
        ct->add_ip_port(ce, saddr, port);
    }else{
        if (verbosity >= 2) {
            printf("Can't get ip/port from SDP:\n%s\n\n", sdp);
            return -1;
        }
    }
    unsigned char rtpmap_event = sdp_get_rtpmap_event(sdp);
    if (rtpmap_event) {
        ce->rtpmap_event = rtpmap_event;
    }
    if (opt_t38only && memmem(sdp, sdplen, "udptl t38", 9)!=NULL){
        ce->had_t38=1;
    }
    return 0;
}

int main(int argc, char *argv[])
{

	pcap_t *handle;/* Session handle */
	// directory/filename template, where .pcap files are written
	char *opt_fntemplate;
	std::string as_call_id_tag;
	char *ifname;/* interface to sniff on */
	char *fname;/* pcap file to read on */
	char errbuf[PCAP_ERRBUF_SIZE];/* Error string */
	struct bpf_program fp;/* The compiled filter */
#ifdef USE_TCP
	char filter_exp[MAX_PCAP_FILTER_EXPRESSION] = "udp or tcp or vlan";/* The filter expression */
#else
	char filter_exp[MAX_PCAP_FILTER_EXPRESSION] = "udp or vlan";/* The filter expression */
#endif
	struct pcap_pkthdr *pkt_header; /* The header that pcap gives us */
	const u_char *pkt_data; /* The actual packet */
	unsigned long last_cleanup = 0;
	int res;
	int offset_to_ip = 0;
	int opt_fork = 1;
	int opt_promisc = 1;
	int opt_packetbuffered = 0;
	int opt_rtpsave = RTPSAVE_RTP_RTCP;
	int opt_call_skip_n = 1; /* By default, record every first call, i.e. record all */
	int call_skip_cnt = 1;
	int opt_pcap_buffer_size = 0; /* Operating system capture buffer size, a.k.a. libpcap ring buffer size */
	int opt_absolute_timeout = INT32_MAX;
	bool number_filter_matched = false;
	bool number_filter_in_use = false;
	regex_t number_filter, method_filter;
	regmatch_t pmatch[1];
	const char *pid_file = "/var/run/pcapsipdump.pid";

	if ((int)getuid()) {
		struct passwd *pw = getpwuid(getuid());
		opt_fntemplate = (char *)malloc(512);
		strncpy(opt_fntemplate, pw->pw_dir, 512);
		strncat(opt_fntemplate, "/.var/spool/pcapsipdump/%Y%m%d/%H/%Y%m%d-%H%M%S-%f-%t-%i.pcap", 512 - 62 - 1);
	}
	else {
		opt_fntemplate = (char *)"/var/spool/pcapsipdump/%Y%m%d/%H/%Y%m%d-%H%M%S-%f-%t-%i.pcap";
	}
	ifname = NULL;
	fname = NULL;
	regcomp(&method_filter, "^(INVITE|OPTIONS|REGISTER)$", REG_EXTENDED);
	trigger.init();

	while (1) {
		signed char c;

		c = getopt(argc, argv, "a:i:r:d:v:m:n:R:l:B:T:t:P:fpU");
		if (c == -1)
			break;

		switch (c) {
		case 'a':
			as_call_id_tag = optarg; as_call_id_tag.append(":");
			break;
		case 'i':
			ifname = optarg;
			break;
		case 'v':
			verbosity = atoi(optarg);
			trigger.verbosity = verbosity;
			break;
		case 'm':
			regfree(&method_filter);
			regcomp(&method_filter, optarg, REG_EXTENDED);
			break;
		case 'n':
			regcomp(&number_filter, optarg, REG_EXTENDED);
			number_filter_in_use = true;
			break;
		case 'R':
			if (strcasecmp(optarg, "none") == 0) {
				opt_rtpsave = RTPSAVE_NONE;
			}
			else if (strcasecmp(optarg, "rtpevent") == 0) {
				opt_rtpsave = RTPSAVE_RTPEVENT;
			}
			else if (strcasecmp(optarg, "t38") == 0) {
				opt_t38only = 1;
			}
			else if (strcasecmp(optarg, "all") == 0 ||
				strcasecmp(optarg, "rtp+rtcp") == 0) {
				opt_rtpsave = RTPSAVE_RTP_RTCP;
			}
			else if (strcasecmp(optarg, "rtp") == 0) {
				opt_rtpsave = RTPSAVE_RTP;
			}
			else {
				printf("Unrecognized RTP filter specification: '%s'\n", optarg);
				return 1;
			}
			break;
		case 'l':
			opt_call_skip_n = atoi(optarg);
			if (opt_call_skip_n < 1) {
				fprintf(stderr, "Invalid option '-l %s'. Argument should be positive integer.\n", optarg);
				return(1);
			}
			break;
		case 'B':
			opt_pcap_buffer_size = parse_size_string(optarg);
			if (opt_pcap_buffer_size < 1) {
				fprintf(stderr, "Invalid option '-B %s'.\n"
					"  Argument should be positive integer with optional quantifier.\n"
					"  e.g.: '-B 32768' or '-B 10KB' or '-b 512MiB', etc.\n", optarg);
				return(1);
			}
			break;
		case 'r':
			fname = optarg;
			break;
		case 'd':
			opt_fntemplate = optarg;
			break;
		case 'f':
			opt_fork = 0;
			break;
		case 'p':
			opt_promisc = 0;
			break;
		case 'U':
			opt_packetbuffered = 1;
			break;
		case 'T':
			opt_absolute_timeout = atol(optarg);
			break;
		case 't':
			trigger.add(optarg);
			break;
		case 'P':
			pid_file = optarg;
			break;
		}
	}

	// everything that is left unparsed in argument string is pcap filter expression
	if (optind < argc) {
		filter_exp[0] = '\0';
		while (optind < argc) {
			if (filter_exp[0] != '\0') {
				strcat(filter_exp, " ");
			}
			strcat(filter_exp, argv[optind++]);
		}
	}

	if ((fname == NULL) && (ifname == NULL)) {
		printf("pcapsipdump version %s\n"
			"Usage: pcapsipdump [-a X-cid][-fpU] [-i interface | -r file] [-d output_directory] [-P pid_file]\n"
			"                   [-v level] [-R filter] [-m filter] [-n filter] [-l filter]\n"
			"                   [-B size] [-T limit] [-t trigger:action:param] [expression]\n"
			" -a   Specify head as Call-ID.\n"
			" -f   Do not fork or detach from controlling terminal.\n"
			" -P   When forking, save PID to this file (default /var/run/pcapsipdump.pid).\n"
			" -p   Do not put the interface into promiscuous mode.\n"
			" -U   Make .pcap files writing 'packet-buffered' - slower method,\n"
			"      but you can use partitially written file anytime, it will be consistent.\n"
			" -i   Specify network interface name (i.e. eth0, em1, ppp0, etc).\n"
			" -r   Read from .pcap file instead of network interface.\n"
			" -v   Set verbosity level (higher is more verbose).\n"
			" -B   Set the operating system capture buffer size, a.k.a. ring buffer size.\n"
			"      This can be expressed in bytes/KB(*1000)/KiB(*1024)/MB/MiB/GB/GiB. ex.: '-B 64MiB'\n"
			"      Set this to few MiB or more to avoid packets dropped by kernel.\n"
			" -R   RTP filter. Specifies what kind of RTP information to include in capture:\n"
			"      'rtp+rtcp' (default), 'rtp', 'rtpevent', 't38', or 'none'.\n"
			" -m   Method-filter. Default is '^(INVITE|OPTIONS|REGISTER)$'\n"
			" -n   Number-filter. Only calls to/from specified number will be recorded\n"
			"      Argument is a regular expression. See 'man 7 regex' for details.\n"
			" -l   Record only each N-th call (i.e. '-l 3' = record only each third call)\n"
			" -d   Set directory (or filename template), where captured files will be stored.\n"
			"      ex.: -d /var/spool/pcapsipdump/%%Y%%m%%d/%%H/%%Y%%m%%d-%%H%%M%%S-%%f-%%t-%%i.pcap\n"
			" -T   Unconditionally stop recording a call after it was active for this many seconds.\n"
			"      Might be useful for broken peers that keep sending RTP long after call ended.\n"
			" -t   <trigger>:<action>:<parameter>. Parameter is %%-expanded (see below)\n"
			"      Triggers: open = when opening a new .pcap file; close = when closing\n"
			"      Actions and their parameters:\n"
			"      mv:<directory> - move .pcap files to <directory> (using /bin/mv)\n"
			"      exec:\"/bin/blah args...\" - fork and execute /bin/blah with arguments\n"
			"      sh:\"shell code\" - fork and execute /bin/sh -c \"shell code\"\n"
			" *    Following %%-codes are expanded in -d and -t: %%f (from/caller), %%t (to/callee),\n"
			"      %%i (call-id), and call date/time (see 'man 3 strftime' for details)\n"
			" *    Trailing argument is pcap filter expression syntax, see 'man 7 pcap-filter'\n"
			, PCAPSIPDUMP_VERSION);
		return 1;
	}

	if ((res = opts_sanity_check_d(&opt_fntemplate)) != 0) return res;
	ct = new calltable;
	ct->opt_absolute_timeout = opt_absolute_timeout;
	if (opt_t38only) {
		ct->erase_non_t38 = 1;
	}
	signal(SIGINT, sigint_handler);
	signal(SIGTERM, sigterm_handler);

	if (verbosity >= 3) {
		printf("Using pcap filter: '%s'\n", filter_exp);
	}

	if (ifname) {
		handle = pcap_create(ifname, errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open interface '%s': pcap_create(): %s\n", ifname, errbuf);
			return(2);
		}
		if (pcap_set_snaplen(handle, 9000)) {
			fprintf(stderr, "Couldn't open interface '%s': pcap_set_snaplen(9000): %s\n", ifname, pcap_geterr(handle));
			return(2);
		}
		if (pcap_set_promisc(handle, opt_promisc)) {
			fprintf(stderr, "Couldn't open interface '%s': pcap_set_promisc(opt_promisc): %s\n", ifname, pcap_geterr(handle));
			return(2);
		}
		if (pcap_set_timeout(handle, 1000)) {
			fprintf(stderr, "Couldn't open interface '%s': pcap_set_timeout(1000):) %s\n", ifname, pcap_geterr(handle));
			return(2);
		}
		printf("Capturing on interface: %s\n", ifname);
		if (opt_pcap_buffer_size > 0) {
			/* setting pcap_set_buffer_size to bigger values helps to deal with packet drops under high load
			   libpcap > 1.0.0 if required for pcap_set_buffer_size
			   for libpcap < 1.0.0 instead, this should be controled by /proc/sys/net/core/rmem_default
			*/
			if (pcap_set_buffer_size(handle, opt_pcap_buffer_size)) {
				fprintf(stderr, "Couldn't open interface '%s': pcap_set_buffer_size(): %s\n", ifname, pcap_geterr(handle));
				return(2);
			}
		}
		if (pcap_activate(handle)) {
			fprintf(stderr, "Couldn't open interface '%s': pcap_activate(): %s\n", ifname, pcap_geterr(handle));
			return(2);
		}
	}
	else {
		printf("Reading file: %s\n", fname);
		handle = pcap_open_offline(fname, errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open pcap file '%s': %s\n", fname, errbuf);
			return(2);
		}
	}

	/* Compile and apply the filter */
	if (pcap_compile(handle, &fp, filter_exp, 0, PCAP_NETMASK_UNKNOWN) == -1) {
		fprintf(stderr, "Couldn't parse filter '%s': %s\n", filter_exp, pcap_geterr(handle));
		return(2);
	}
	if (pcap_setfilter(handle, &fp) == -1) {
		fprintf(stderr, "Couldn't install filter '%s': %s\n", filter_exp, pcap_geterr(handle));
		return(2);
	}

	{
		int dlt = pcap_datalink(handle);
		switch (dlt) {
		case DLT_EN10MB:
			offset_to_ip = sizeof(struct ether_header);
			break;
		case DLT_LINUX_SLL:
			offset_to_ip = 16;
			break;
		case DLT_RAW:
			offset_to_ip = 0;
			break;
		default: {
			fprintf(stderr, "Unknown interface type (%d).\n", dlt);
			return 3;
		}
		}
	}

	if (opt_fork) {
		// daemonize

		pid_t pid = fork();
		if (pid) {
			FILE *fp = fopen(pid_file, "w");
			if (fp) {
				fprintf(fp, "%d\n", pid);
				fclose(fp);
				exit(0);
			}
			else {
				fprintf(stderr, "Can't write PID %d to file %s\n",
					pid, pid_file);
				return(2);
			}
		}
	}

	/* Retrieve the packets */
	while ((res = pcap_next_ex(handle, &pkt_header, &pkt_data)) >= 0)
	{
		struct linux_cooked_capture * linux_c_c;
		struct iphdr *header_ip;
		struct ipv6hdr *header_ipv6;
		char *data;
		const char *s;
		unsigned long datalen;
		unsigned long taglen;
		bool is_sip_method = false;
		char sip_method[10] = "";
		bool is_sip_response = false;
		int sip_status = 0;

		if (res == 0)
			/* Timeout elapsed */
			continue;

		if (pkt_header->ts.tv_sec - last_cleanup > 15) {
			ct->do_cleanup(pkt_header->ts.tv_sec);
			last_cleanup = pkt_header->ts.tv_sec;
		}
		if (offset_to_ip == 16) {
			linux_c_c = (linux_cooked_capture *)pkt_data;
		}
		else {
			linux_c_c = NULL;
		}

		header_ip = (iphdr *)((char*)pkt_data + offset_to_ip);
		// 802.1Q VLAN
		if ((offset_to_ip == 14) &&
			ntohs(*((uint16_t*)((char*)pkt_data + offset_to_ip - 2))) == 0x8100 &&
			ntohs(*((uint16_t*)((char*)pkt_data + offset_to_ip + 2))) == 0x0800) {
			header_ip = (iphdr *)((char*)pkt_data + offset_to_ip + 4);
		}
		header_ipv6 = (ipv6hdr *)header_ip;
		if (header_ip->version == 4 && (header_ip->frag_off & htons(0x1fff)) > 0) { // fragment offset > 0
			struct addr_addr_id aai = (struct addr_addr_id) {
				header_ip->saddr,
					header_ip->daddr,
					header_ip->id
			};
			pcap_dumper_t *f = ct->get_ipfrag(aai);
			if (f) {
				pcap_dump((u_char *)f, pkt_header, pkt_data);
				if (opt_packetbuffered) {
					pcap_dump_flush(f);
				}
				if ((header_ip->frag_off & htons(0x2000)) == 0) { // more_fragments == 0
					ct->delete_ipfrag(aai);
				}
			}
		}
		else if ( /* sane IPv4 UDP */
			(header_ip->version == 4 && pkt_header->caplen >=
			(offset_to_ip + sizeof(struct iphdr) + sizeof(struct udphdr)) &&
				header_ip->protocol == IPPROTO_UDP)
			/* sane IPv6 UDP */ ||
			(header_ipv6->version == 6 && pkt_header->caplen >=
			(offset_to_ip + sizeof(struct ipv6hdr) + sizeof(struct udphdr)) &&
				header_ipv6->nexthdr == IPPROTO_UDP)
#ifdef USE_TCP
			/* sane IPv4 TCP */ ||
			(header_ip->version == 4 && pkt_header->caplen >=
			(offset_to_ip + sizeof(struct iphdr) + sizeof(struct tcphdr)) &&
				header_ip->protocol == IPPROTO_TCP)
			/* sane IPv6 TCP */ ||
			(header_ipv6->version == 6 && pkt_header->caplen >=
			(offset_to_ip + sizeof(struct ipv6hdr) + sizeof(struct tcphdr)) &&
				header_ip->protocol == IPPROTO_TCP)
#endif
			) {
			calltable_element_ptr ce = nullptr;
			int save_this_rtp_packet = 0;
			int is_rtcp = 0;
			uint16_t rtp_port_mask = 0xffff;
			struct udphdr *header_udp;
			struct tcphdr *tcph;

			tcph = (tcphdr *)((char*)header_ip +
				((header_ip->version == 4) ? sizeof(iphdr) : sizeof(ipv6hdr)));
			header_udp = (udphdr *)((char*)header_ip +
				((header_ip->version == 4) ? sizeof(iphdr) : sizeof(ipv6hdr)));

			if ((header_ip->version == 4 && header_ip->protocol == IPPROTO_UDP) ||
				(header_ip->version == 6 && header_ipv6->nexthdr == IPPROTO_UDP)) {
				data = (char *)header_udp + sizeof(*header_udp);
			}
			else {
				data = (char *)((unsigned char *)tcph + (tcph->doff * 4));
			}
			datalen = pkt_header->caplen - ((unsigned long)data - (unsigned long)pkt_data);

			if (opt_rtpsave == RTPSAVE_RTP || opt_rtpsave == RTPSAVE_RTPEVENT) {
				save_this_rtp_packet = 1;
			}
			else if (opt_rtpsave == RTPSAVE_RTP_RTCP) {
				save_this_rtp_packet = 1;
				rtp_port_mask = 0xfffe;
				is_rtcp = (htons(header_udp->source) & 1) && (htons(header_udp->dest) & 1);
			}
			else {
				save_this_rtp_packet = 0;
			}

			is_sip_method = get_method(data, sip_method, sizeof(sip_method));
			is_sip_response = get_status(data, MIN(datalen, 32), &sip_status);
			if(!is_sip_method && !is_sip_response){
				ce = ct->find_ip_port(hdaddr(header_ip), htons(header_udp->dest) & rtp_port_mask);

				if (save_this_rtp_packet && ce != nullptr)
				{
					if (ce->f_pcap != NULL &&
						(opt_rtpsave != RTPSAVE_RTPEVENT ||
							data[1] == ce->rtpmap_event)) {
						ce->last_packet_time = pkt_header->ts.tv_sec;
						pcap_dump((u_char *)ce->f_pcap, pkt_header, pkt_data);
						if (opt_packetbuffered) {
							pcap_dump_flush(ce->f_pcap);
						}
					}
					continue;
				}

				ce = ct->find_ip_port(hsaddr(header_ip), htons(header_udp->source) & rtp_port_mask);
				if (save_this_rtp_packet && ce != nullptr)
				{
					if (ce->f_pcap != NULL &&
						(opt_rtpsave != RTPSAVE_RTPEVENT || data[1] == ce->rtpmap_event)) {
						ce->last_packet_time = pkt_header->ts.tv_sec;
						pcap_dump((u_char *)ce->f_pcap, pkt_header, pkt_data);
						if (opt_packetbuffered) {
							pcap_dump_flush(ce->f_pcap);
						}
					}
					continue;
				}
			}
			if (is_sip_method || is_sip_response)
			{
				char caller[256] = "";
				char called[256] = "";
				char callid[512] = "";

				if (get_sip_peername(data, datalen, "From:", caller, sizeof(caller))) {
					get_sip_peername(data, datalen, "f:", caller, sizeof(caller));
				}
				if (get_sip_peername(data, datalen, "To:", called, sizeof(called))) {
					get_sip_peername(data, datalen, "t:", called, sizeof(called));
				}
				std::string as_call_id;
				if(!as_call_id_tag.empty()){
					const char* as_call_id_tmp = gettag(data, datalen, as_call_id_tag.c_str(), &taglen);
					if(as_call_id_tmp){
						as_call_id = std::string(as_call_id_tmp, taglen);
					}
				}
				
				const char * tmp = gettag(data, datalen, "Call-ID:", &taglen) ? : gettag(data, datalen, "i:", &taglen);
				memcpy(callid, tmp, MIN(taglen, sizeof(callid)));
				callid[MIN(taglen, sizeof(callid))] = '\0';
				number_filter_matched = false;
				if (!number_filter_in_use ||
					(regexec(&number_filter, caller, 1, pmatch, 0) == 0) ||
					(regexec(&number_filter, called, 1, pmatch, 0) == 0)) {
					number_filter_matched = true;
				}
				if (callid[0] != '\0' && (ce = ct->find_by_call_id(callid, as_call_id)) == nullptr && number_filter_matched)
				{
					ce = ct->add(callid, caller, called, pkt_header->ts.tv_sec);
					if (ce == nullptr) {
						printf("Too many simultaneous calls. Ran out of call table space!\n");
					}
					else {
						if (regexec(&method_filter, sip_method, 1, pmatch, 0) == 0) {
							if ((--call_skip_cnt) > 0) {
								if (verbosity >= 3) {
									printf("Skipping %s call from %s to %s \n", sip_method, caller, called);
								}
								ce->f_pcap = NULL;
							}
							else {
								char fn[1024], dn[1024];
								std::string callid_tmp = callid;
								if (!as_call_id.empty()) {
									callid_tmp.append("-").append(as_call_id);
								}
								call_skip_cnt = opt_call_skip_n;
								expand_dir_template(fn, sizeof(fn), opt_fntemplate,
									caller, called, callid_tmp.c_str(),
									pkt_header->ts.tv_sec);
								if (strchr(fn, '/')) {
									strcpy(dn, fn);
									mkdir_p(dirname(dn), 0777);
								}
								ce->f_pcap = pcap_dump_open(handle, fn);
								ce->fn_pcap = fn;
								if (verbosity >= 3) {
									printf("Capture Call-ID:%s, %s:%s\n", callid, as_call_id_tag.c_str(), as_call_id.c_str());
								}
							}

						}
						else {
							if (verbosity >= 2) {
								printf("Unknown SIP method:'%s'!\n", sip_method);
							}
							ce->f_pcap = NULL;
						}
					}
				}

				if (ce != nullptr) {
					char *sdp = NULL;
					if (strcmp(sip_method, "BYE") == 0 || sip_status >= 300) {
						ce->had_bye = 1;
					}
					s = gettag(data, datalen, "Content-Type:", &taglen) ? :
						gettag(data, datalen, "c:", &taglen);
					if (taglen > 0 && s && strncasecmp(s, "application/sdp", taglen) == 0 &&
						(sdp = strstr(data, "\r\n\r\n")) != NULL && datalen > (sdp - data)) {
						if(linux_c_c){
							if(ntohs(linux_c_c->type) == 4) {//sent by us
								parse_sdp(hsaddr(header_ip), sdp, datalen - (sdp - data), ce);
							}
						}
						else {
							parse_sdp(hsaddr(header_ip), sdp, datalen - (sdp - data), ce);
						}
					}
					else if (taglen > 0 && s && strncasecmp(s, "multipart/mixed;boundary=", MIN(taglen, 25)) == 0 &&
						(sdp = strstr(data, "\r\n\r\n")) != NULL && datalen > (sdp - data)) {
						// FIXME: do proper mime miltipart parsing
						if (linux_c_c) {
							if (ntohs(linux_c_c->type) == 4) {//sent by us
								parse_sdp(hsaddr(header_ip), sdp, datalen - (sdp - data), ce);
							}
						}
						else {
							parse_sdp(hsaddr(header_ip), sdp, datalen - (sdp - data), ce);
						}
					}
					if (ce->f_pcap != NULL) {
						pcap_dump((u_char *)ce->f_pcap, pkt_header, pkt_data);
						ce->last_packet_time = pkt_header->ts.tv_sec;
						if (opt_packetbuffered) {
							pcap_dump_flush(ce->f_pcap);
						}
					}
					if (header_ip->version == 4 && header_ip->frag_off == htons(0x2000)) { //flags == more fragments and offset == 0
						ct->add_ipfrag((struct addr_addr_id) {
							header_ip->saddr,
								header_ip->daddr,
								header_ip->id
						}, ce->f_pcap);
					}
				}
				else if (verbosity >= 3) {
					printf("Skipping udp packet SIP method:'%s' or SIP response %d \n", sip_method, sip_status);
				}
			}
			else {
				if (verbosity >= 3) {
					char st1[INET6_ADDRSTRLEN];
					char st2[INET6_ADDRSTRLEN];

					if (header_ip->version == 4) {
						inet_ntop(AF_INET, &(header_ip->saddr), st1, sizeof(st1));
						inet_ntop(AF_INET, &(header_ip->daddr), st2, sizeof(st2));
					}
					else {
						inet_ntop(AF_INET6, &(header_ipv6->saddr), st1, sizeof(st1));
						inet_ntop(AF_INET6, &(header_ipv6->daddr), st2, sizeof(st2));
					}
					printf("Skipping udp packet %s:%d->%s:%d\n",
						st1, htons(header_udp->source),
						st2, htons(header_udp->dest));
				}
			}
		}

	}
	// flush / close files
	ct->do_cleanup(INT64_MAX);
	// close libpcap session
	pcap_close(handle);
	// wait for forked processes;
	{
		int status;
		waitpid(-1, &status, 0);
	}
	return(0);
}

int get_sip_peername(char *data, int data_len, const char *tag, char *peername, int peername_len) {
	unsigned long r, r2, peername_tag_len;
	const char *peername_tag = gettag(data, data_len, tag, &peername_tag_len);
	if ((r = (unsigned long)memmem(peername_tag, peername_tag_len, "sip:", 4)) == 0) {
		goto fail_exit;
	}
	r += 4;
	if ((r2 = (unsigned long)memmem(peername_tag, peername_tag_len, "@", 1)) == 0) {
		goto fail_exit;
	}
	if (r2 <= r) {
		goto fail_exit;
	}
	memcpy(peername, (void*)r, r2 - r);
	memset(peername + (r2 - r), 0, 1);
	return 0;
fail_exit:
	strcpy(peername, "empty");
	return 1;
}

int get_ip_port_from_sdp(const char *sdp, size_t sdplen, in_addr_t *addr, unsigned short *port) {
	unsigned long l;
	const char *s;
	char s1[20];
	s = gettag(sdp, sdplen, "c=IN IP4 ", &l);
	memset(s1, '\0', sizeof(s1));
	memcpy(s1, s, MIN(l, 19));
	if ((long)(*addr = inet_addr(s1)) == -1) {
		*addr = 0;
		*port = 0;
		return 1;
	}
	s = gettag(sdp, sdplen, "m=audio ", &l);
	if (l == 0) {
		s = gettag(sdp, sdplen, "m=image ", &l);
	}
	if (l == 0 || (*port = atoi(s)) == 0) {
		*port = 0;
		return 1;
	}
	return 0;
}

inline uint32_t get_ssrc (void *udp_packet_data_pointer, bool is_rtcp){
    if (is_rtcp){
        return ntohl(*(uint32_t*)((uint8_t*)udp_packet_data_pointer+4));
    }else{
        return ntohl(*(uint32_t*)((uint8_t*)udp_packet_data_pointer+8));
    }
}

long long parse_size_string(char *s) {
	char multiplier[32];
	long long result;
	int i;

	struct multiplier_element {
		char text[32];
		unsigned long value;
	} multipliers[] = {
		{"",1},
		{"b",1},
		{"byte",1},
		{"bytes",1},
		{"kb",1000},
		{"kib",1024},
		{"mb",1000 * 1000},
		{"mib",1024 * 1024},
		{"gb",1000 * 1000 * 1000},
		{"gib",1024 * 1024 * 1024},
		{"",0}
	};

	if (strlen(s) >= 32) {
		return 0;
	}
	result = 0;
	multiplier[0] = 0;
	sscanf(s, "%lld%s", &result, multiplier);
	for (i = 0; multipliers[i].value > 0; i++) {
		if (strcasecmp(multipliers[i].text, multiplier) == 0) {
			result *= multipliers[i].value;
			return result;
		}
	}
	return 0;
}

#ifndef _GNU_SOURCE
void *memmem(const void* haystack, size_t hl, const void* needle, size_t nl) {
	int i;

	if (nl > hl) return 0;
	for (i = hl - nl + 1; i; --i) {
		if (!memcmp(haystack, needle, nl)) {
			return (char*)haystack;
		}
		haystack = (void*)((char*)haystack + 1);
	}
	return 0;
}
#endif
